let shouldLog = null

/**
 * This method checks if a security message should be logged.
 * It does so by determining whether we're running as Electron,
 * which indicates that a developer is currently looking at the
 * app.
 *
 * @returns {boolean} - Should we log?
 */
const shouldLogSecurityWarnings = function () {
  if (shouldLog !== null) {
    return shouldLog
  }

  const { platform, execPath, env } = process

  switch (platform) {
    case 'darwin':
      shouldLog = execPath.endsWith('MacOS/Electron') ||
                  execPath.includes('Electron.app/Contents/Frameworks/')
      break
    case 'freebsd':
    case 'linux':
      shouldLog = execPath.endsWith('/electron')
      break
    case 'win32':
      shouldLog = execPath.endsWith('\\electron.exe')
      break
    default:
      shouldLog = false
  }

  if ((env && env.ELECTRON_DISABLE_SECURITY_WARNINGS) ||
      (window && window.ELECTRON_DISABLE_SECURITY_WARNINGS)) {
    shouldLog = false
  }

  if ((env && env.ELECTRON_ENABLE_SECURITY_WARNINGS) ||
      (window && window.ELECTRON_ENABLE_SECURITY_WARNINGS)) {
    shouldLog = true
  }

  return shouldLog
}

/**
 * Check's if the current window is remote.
 *
 * @returns {boolean} - Is this a remote protocol?
 */
const getIsRemoteProtocol = function () {
  if (window && window.location && window.location.protocol) {
    return /^(http|ftp)s?/gi.test(window.location.protocol)
  }
}

/**
 * Tries to determine whether a CSP without `unsafe-eval` is set.
 *
 * @returns {boolean} Is a CSP with `unsafe-eval` set?
 */
const isUnsafeEvalEnabled = function () {
  try {
    //eslint-disable-next-line
    new Function('');
    return true
  } catch (error) {
    return false
  }
}

/**
 * Attempts to get the current webContents. Returns null
 * if that fails
 *
 * @returns {Object|null} webPreferences
 */
let webPreferences
const getWebPreferences = function () {
  try {
    if (webPreferences) {
      return webPreferences
    }

    const { remote } = require('electron')
    webPreferences = remote.getCurrentWebContents().getWebPreferences()
    return webPreferences
  } catch (error) {
    return null
  }
}

const moreInformation = '\nFor more information and help, consult ' +
                        'https://electronjs.org/docs/tutorial/security.\n' +
                        'This warning will not show up once the app is packaged.'

module.exports = {
  shouldLogSecurityWarnings,

  /**
   * #1 Only load secure content
   *
   * Checks the loaded resources on the current page and logs a
   * message about all resources loaded over HTTP or FTP.
   */
  warnAboutInsecureResources: () => {
    if (!window || !window.performance || !window.performance.getEntriesByType) {
      return
    }

    const resources = window.performance
      .getEntriesByType('resource')
      .filter(({ name }) => /^(http|ftp):?/gi.test(name || ''))
      .map(({ name }) => `- ${name}`)
      .join('\n')

    if (!resources || resources.length === 0) {
      return
    }

    let warning = 'This renderer process loads resources using insecure protocols. ' +
                  'This exposes users of this app to unnecessary security risks. ' +
                  'Consider loading the following resources over HTTPS or FTPS. \n' +
                  resources + '\n' +
                  moreInformation

    console.warn('%cElectron Security Warning (Insecure Resources)',
      'font-weight: bold;', warning)
  },

  /**
   * #2 on the checklist: Disable the Node.js integration in all renderers that
   * display remote content
   *
   * Logs a warning message about Node integration.
   */
  warnAboutNodeWithRemoteContent: () => {
    if (getIsRemoteProtocol()) {
      let warning = 'This renderer process has Node.js integration enabled ' +
                    'and attempted to load remote content. This exposes users of this app to severe ' +
                    'security risks.\n' +
                    moreInformation

      console.warn('%cElectron Security Warning (Node.js Integration with Remote Content)',
        'font-weight: bold;', warning)
    }
  },

  // Currently missing since it has ramifications and is still experimental:
  //   #3 Enable context isolation in all renderers that display remote content
  //
  // Currently missing since we can't easily programmatically check for those cases:
  //   #4 Use ses.setPermissionRequestHandler() in all sessions that load remote content

  /**
   * #5 on the checklist: Do not disable websecurity
   *
   * Logs a warning message about disabled webSecurity.
   */
  warnAboutDisabledWebSecurity: () => {
    const webPreferences = getWebPreferences()
    if (!webPreferences || webPreferences.webSecurity !== false) return

    let warning = 'This renderer process has "webSecurity" disabled. ' +
                  'This exposes users of this app to severe security risks.\n' +
                  moreInformation

    console.warn('%cElectron Security Warning (Disabled webSecurity)',
      'font-weight: bold;', warning)
  },

  /**
   * #6 on the checklist: Define a Content-Security-Policy and use restrictive
   * rules (i.e. script-src 'self')
   *
   * #7 on the checklist: Disable eval
   *
   * Logs a warning message about unset or insecure CSP
   */
  warnAboutInsecureCSP: () => {
    if (isUnsafeEvalEnabled()) {
      let warning = 'This renderer process has either no Content Security Policy set ' +
                    'or a policy with "unsafe-eval" enabled. This exposes users of this ' +
                    'app to unnecessary security risks.\n' +
                    moreInformation

      console.warn('%cElectron Security Warning (Insecure Content-Security-Policy)',
        'font-weight: bold;', warning)
    }
  },

  /**
   * #8 on the checklist: Do not set allowRunningInsecureContent to true
   *
   * Logs a warning message about disabled webSecurity.
   */
  warnAboutInsecureContentAllowed: () => {
    const webPreferences = getWebPreferences()
    if (!webPreferences || !webPreferences.allowRunningInsecureContent) return

    let warning = 'This renderer process has "allowRunningInsecureContent" ' +
                  'enabled. This exposes users of this app to severe security risks.\n' +
                  moreInformation

    console.warn('%cElectron Security Warning (allowRunningInsecureContent)',
      'font-weight: bold;', warning)
  },

  /**
   * #9 on the checklist: Do not enable experimental features
   *
   * Logs a warning message about experimental features.
   */
  warnAboutExperimentalFeatures: () => {
    const webPreferences = getWebPreferences()
    if (!webPreferences || (!webPreferences.experimentalFeatures &&
        !webPreferences.experimentalCanvasFeatures)) {
      return
    }

    let warning = 'This renderer process has "experimentalFeatures" ' +
                  'enabled. This exposes users of this app to some security risk. ' +
                  'If you do not need this feature, you should disable it.\n' +
                  moreInformation

    console.warn('%cElectron Security Warning (experimentalFeatures)',
      'font-weight: bold;', warning)
  },

  /**
   * #10 on the checklist: Do not use blinkFeatures
   *
   * Logs a warning message about blinkFeatures
   */
  warnAboutBlinkFeatures: () => {
    const webPreferences = getWebPreferences()
    if (!webPreferences || !webPreferences.blinkFeatures ||
        (webPreferences.blinkFeatures.length && webPreferences.blinkFeatures.length === 0)) {
      return
    }

    let warning = 'This renderer process has additional "blinkFeatures" ' +
                  'enabled. This exposes users of this app to some security risk. ' +
                  'If you do not need this feature, you should disable it.\n' +
                  moreInformation

    console.warn('%cElectron Security Warning (blinkFeatures)',
      'font-weight: bold;', warning)
  },

  /**
   * #11 on the checklist: Do Not Use allowpopups
   *
   * Logs a warning message about allowed popups
   */
  warnAboutAllowedPopups: () => {
    if (document && document.querySelectorAll) {
      const domElements = document.querySelectorAll('[allowpopups]')

      if (!domElements || domElements.length === 0) {
        return
      }

      let warning = 'A <webview> has "allowpopups" set to true. ' +
                    'This exposes users of this app to some security risk, since popups are just ' +
                    'BrowserWindows. If you do not need this feature, you should disable it.\n' +
                    moreInformation

      console.warn('%cElectron Security Warning (allowpopups)',
        'font-weight: bold;', warning)
    }
  }

  // Currently missing since we can't easily programmatically check for it:
  //   #12WebViews: Verify the options and params of all `<webview>` tags
}
